package com.dilean.impl.model.attrval;

import java.io.File;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.dilean.api.dao.AttributeDAO;
import com.dilean.api.dao.AttributeListValueDAO;
import com.dilean.api.model.Attribute;
import com.dilean.api.model.AttributeListValue;
import com.dilean.api.model.AttributeValue;
import com.dilean.api.model.AttributeValueFactory;

import static com.dilean.common.DileanAttrTypes.*;

@Component
public class AttrValueFactoryImpl implements AttributeValueFactory
{
    @Autowired
    private AttributeDAO attrDAO;
    
    @Autowired
    private AttributeListValueDAO attrListValueDAO;

    private final static DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
    
    private final Log log = LogFactory.getLog(AttrValueFactoryImpl.class);
    
    
    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeValue<T> createValue(Integer personId, Integer attrId, List<Object[]> params)
    {
        final Attribute attr = attrDAO.findById(attrId);
        
        if (params == null || params.isEmpty())
            return null;
        
        if (!attr.isMultiple() && params.size() > 1)
        {
            throw new IllegalStateException("There are " + params.size() + " values were found for " + 
                    "person : " + personId + " and single attr : " + attrId + ". Values are " + 
                    Arrays.toString(params.get(0)) + ", " + Arrays.toString(params.get(1)) + " ... ");
        }
        
        final int attrType = attr.getAttributeType().getId();
        final boolean isMult = attr.isMultiple();
        final T value;
        final AttributeValue<T> result;

        if (isMult)
        {
            switch (attrType) 
            {
                case ATTR_TYPE_ID_NUM : 
                {
                    result = (AttributeValue<T>) new AttrValueNumberMultiple();
                    value = (T) new ArrayList<BigInteger>(params.size());
                    for (Object[] param : params)
                        ((List<BigInteger>) value).add(new BigInteger(param[1].toString()));
                    
                    break;
                }
                case ATTR_TYPE_ID_TEXT : 
                {
                    result = (AttributeValue<T>) new AttrValueTextMultiple();
                    value = (T) new ArrayList<String>(params.size());
                    for (Object[] param : params)
                        ((List<String>) value).add(param[1].toString());
                    
                    break;
                }
                case ATTR_TYPE_ID_LIST : 
                {
                    result = (AttributeValue<T>) new AttrValueListMultiple();
                    value = (T) new ArrayList<AttributeListValue>(params.size());
                    for (Object[] param : params)
                    {
                        final AttributeListValue alv = attrListValueDAO.findById(Integer.valueOf(param[0].toString()));
                        ((List<AttributeListValue>) value).add(alv);
                    }
                    
                    break;
                }
                case ATTR_TYPE_ID_DATE : 
                {
                    result = (AttributeValue<T>) new AttrValueDateMultiple();
                    value = (T) new ArrayList<Date>(params.size());
                    for (Object[] param : params)
                        ((List<Date>) value).add((Date)param[2]); 
                    break;
                }
                case ATTR_TYPE_ID_FILE : 
                {
                    result = (AttributeValue<T>) new AttrValueFileMultiple();
                    value = (T) new ArrayList<File>(params.size());
                    for (Object[] param : params)
                        ((List<File>) value).add(new File (param[1].toString())); 
                    break;
                }
                default:
                    throw new IllegalStateException("Unsupported attr type : " + attrType);
            }
        }
        else
        {
            switch (attrType) 
            {
                case ATTR_TYPE_ID_NUM : 
                {
                    result = (AttributeValue<T>) new AttrValueNumber();
                    value = (T) new BigInteger(params.get(0)[1].toString());
                    break;
                }
                case ATTR_TYPE_ID_TEXT : 
                {
                    result = (AttributeValue<T>) new AttrValueText();
                    value = (T) params.get(0)[1].toString();
                    break;
                }
                case ATTR_TYPE_ID_LIST : 
                {
                    result = (AttributeValue<T>) new AttrValueList();
                    value = (T) attrListValueDAO.findById(Integer.valueOf(params.get(0)[0].toString()));
                    break;
                }
                case ATTR_TYPE_ID_DATE : 
                {
                    result = (AttributeValue<T>) new AttrValueDate();
					value = (T) (Date)(params.get(0)[2]);                    
                    break;
                }
                case ATTR_TYPE_ID_FILE : 
                {
                    result = (AttributeValue<T>) new AttrValueFile();            
					value = (T) (new File(params.get(0)[1].toString()));                    
                    break;
                }
                default:
                    throw new IllegalStateException("Unsupported attr type : " + attrType);
            }
        }
        
        result.setAttribute(attr);
        result.setPersonId(personId);
        result.setValue(value);
        return result;
    }

	@Override
	public <T> Collection<AttributeValue<T>> createValues(Integer personId, List<Object[]> resultList) 
	{
		Collection<AttributeValue<T>> result = new HashSet<AttributeValue<T>>();
		ListIterator<Object[]> i = resultList.listIterator();

		while (i.hasNext()) 
		{
			Object[] param = i.next();
			String attrId = param[3].toString();
			int startIndex = i.nextIndex() - 1;
			
			if (i.hasNext()) 
			{
				String attrIdNext = i.next()[3].toString();
				while (attrId.equals(attrIdNext) && i.hasNext())
					attrIdNext = i.next()[3].toString();

				i.previous();
			}
			int endIndex = i.nextIndex();
			AttributeValue<T> attrValue = createValue(personId, new Integer(param[3].toString()), resultList.subList(startIndex, endIndex));
			result.add(attrValue);
			
		}
		return result;      
	}

	@Override
	public <T> AttributeValue<T> createValueFromString(Integer personId, Integer attrId, Object param) 
	{
		if (param == null)
			return null;
		
		final Attribute attr = attrDAO.findById(attrId);
		
		final List<Object[]> params = new ArrayList<Object[]>(); // LIST_VALUE_ID, VALUE, DATE
        if (attr.isMultiple())
        {
        	final String[] values = param.toString().split(",");
        	switch (attr.getAttributeType().getId()) 
        	{
        		case ATTR_TYPE_ID_LIST : 
        		{
        			for (String value : values)
        				params.add(new Object[]{attrListValueDAO.findByName(value.trim()).iterator().next().getId(), null, null});
        				
        			break;
        		}
        		case ATTR_TYPE_ID_DATE : 
        		{
        			try
        			{
        				for (String value : values)
        					params.add(new Object[]{null, null, dateFormat.parse(value.trim())});
        			}
        			catch(ParseException ex)
        			{
        				log.error("Cannot create DATE value", ex);
        				return null;
        			}
        				
        			break;
        		}
        		default :
        			for (String value : values)
    					params.add(new Object[]{null, value.trim(), null});
        	}	
        }
        else
        	switch (attr.getAttributeType().getId()) 
        	{
        		case ATTR_TYPE_ID_LIST : 
        		{
        			params.add(new Object[]{attrListValueDAO.findByName(param.toString().trim()).iterator().next().getId(), null, null});
        			break;
        		}
        		case ATTR_TYPE_ID_DATE : 
        		{
        			try
        			{
        				params.add(new Object[]{null, null, dateFormat.parse("01.02.1990")});
        			}
        			catch(ParseException ex)
        			{
        				log.error("Cannot create DATE value", ex);
        				return null;
        			}
        			
        			break;
        		}
        		default :
        			params.add(new Object[]{null, param.toString().trim(), null});
			}	
        
        AttributeValue<T> value = null;
        
        try
        {
        	value = createValue(personId, attrId, params);
        }
        catch(Exception ex)
        {
        	throw new RuntimeException("Cannot create value for attr : " + attr + " from " + param, ex);
        }
        
        return value;
	}

}
